/*
 * FakeCanvas.java
 *
 * Created on November 11, 2006, 8:05 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package edu.umn.cs5115.scheduler.framework.gui;

import edu.umn.cs5115.scheduler.framework.DocumentController;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

/**
 * A canvas that can display fake widgets.  Includes code for managing their
 * z-order and making sure mouse events get distributed in the proper fashion.
 *
 * @author grant
 */
public abstract class FakeCanvas extends Canvas
{
    /** This is the root fake widget.  Events are actually forwarded to this
     * widget, which then takes care of everything else.
     */
    RootWidget rootWidget = new RootWidget(this);
    /** Listens for paint events. */
    private PaintListener paintListener = new PaintListener()
    {
        public void paintControl(PaintEvent e)
        {
            FakeCanvas.this.paintControl(e);
            rootWidget.handlePaintEvent(e);
        }
    };
    /** Listens for mouse events. */
    private MouseListener mouseListener = new MouseListener()
    {
        public void mouseDoubleClick(MouseEvent e)
        {
            handleMouseEvent(e, MOUSE_DOUBLE_CLICK_DISPATCHER);
        }
        public void mouseDown(MouseEvent e)
        {
            setFocus();
            handleMouseEvent(e, MOUSE_DOWN_DISPATCHER);
        }
        public void mouseUp(MouseEvent e)
        {
            handleMouseEvent(e, MOUSE_UP_DISPATCHER);
        }
    };
        
    /**
     * Dispatch a mouse event to all affected children until one of them handles
     * it.
     * @param e The mouse event to dispatch.
     * @param dispatcher The dispatcher to use to dispatch it.
     */
    private void handleMouseEvent(MouseEvent e, CanvasDispatcher dispatcher)
    {
        if (!rootWidget.handleMouseEvent(e, dispatcher))
            dispatcher.dispatchToCanvas(e);
    }
    
    /** Used to dispatch Handleable events by FakeCanvas/FakeWidget. */
    static abstract class Dispatcher
    {
        /** Send the event to the fake widget.
         * @param e The event to send.
         * @param widget the widget to send it to.
         * @return True if the event is handled by the widget, false otherwise.
         */
        public abstract boolean dispatchEvent(MouseEvent e, FakeWidget widget);
        /** Should we save the handler to this event? */
        public boolean getShouldSaveHandler() { return false; }
        /** Should we throw away the saved handler after this event? */
        public boolean getShouldRevertToNormal() { return false; }
    }
    
    /** Private subclass of HandleableDispatcher containing method to dispatch event to the canvas. */
    private static abstract class CanvasDispatcher extends Dispatcher
    {
        /** Send the event to the fake canvas. */
        public abstract void dispatchToCanvas(MouseEvent e);
    }
    
    /** Sends mouseDoubleClick events. */
    private final CanvasDispatcher MOUSE_DOUBLE_CLICK_DISPATCHER = new CanvasDispatcher()
    {
        public boolean dispatchEvent(MouseEvent e, FakeWidget widget)
        {
            return widget.mouseDoubleClick(e);
        }
        public void dispatchToCanvas(MouseEvent e)
        {
            FakeCanvas.this.mouseDoubleClick(e);
        }
    };
    /** Sends mouseDown events. */
    private final CanvasDispatcher MOUSE_DOWN_DISPATCHER = new CanvasDispatcher()
    {
        public boolean dispatchEvent(MouseEvent e, FakeWidget widget)
        {
            return widget.mouseDown(e);
        }
        public void dispatchToCanvas(MouseEvent e)
        {
            FakeCanvas.this.mouseDown(e);
        }
        /** Save receiver for a mouse down */
        public boolean getShouldSaveHandler() { return true; }
    };
    /** Sends mouseUp events. */
    private final CanvasDispatcher MOUSE_UP_DISPATCHER = new CanvasDispatcher()
    {
        public boolean dispatchEvent(MouseEvent e, FakeWidget widget)
        {
            return widget.mouseUp(e);
        }
        public void dispatchToCanvas(MouseEvent e)
        {
            FakeCanvas.this.mouseUp(e);
        }
        /** Revert to normal after processing amouse up */
        public boolean getShouldRevertToNormal() { return true; }
    };
    
    /** 
     * Creates a new instance of FakeCanvas 
     * @param parent The parent control.
     * @param style The SWT style to use to create the canvas.
     */
    public FakeCanvas(Composite parent, int style)
    {
        super(parent, style);
        addPaintListener(paintListener);
        addMouseListener(mouseListener);
    }
    
    /**
     * Returns the fake widget that is the root of all children in this canvas.
     * @return The actual root fake widget
     */
    public FakeWidget getRootWidget()
    {
        return rootWidget;
    }
    
    /**
     * Add a fake widget to the list of components.  It is added at the top. 
     * This method is shorthand for getRootWidget().add(FakeWidget)
     * @param fakeWidget The widget to add.
     * @see #getRootWidget()
     * @see FakeWidget#add(FakeWidget)
     */
    public void add(FakeWidget fakeWidget)
    {
        getRootWidget().add(fakeWidget);
    }
    
    /**
     * Remove a fake widget from the list of components.  This is shorthand for
     * getRootWidget().remove()
     * @param fakeWidget the widget to remove.
     * @see #getRootWidget()
     * @see FakeWidget#remove(FakeWidget)
     */
    public void remove(FakeWidget fakeWidget)
    {
        getRootWidget().remove(fakeWidget);
    }
    
    /**
     * Moves the specified control to the front.  This is shorthand for 
     * getRootWidget().bringToFont(fakeWidget)
     * @param fakeWidget The widget to move to the front.  If this widget isn't
     * already in the canvas, this method does nothing.
     * @see #getRootWidget()
     * @see FakeWidget#bringToFront(FakeWidget)
     */
    public void bringToFront(FakeWidget fakeWidget)
    {
        getRootWidget().bringToFront(fakeWidget);
    }
    
    /**
     * Sends the specified control to the back.  This is shorthand for 
     * getRootWidget().sendToBack(FakeWidget)
     * @param fakeWidget The widget to send to the back.  If the widget isn't in
     * the canvas, this method does nothing.
     * @see #getRootWidget()
     * @see FakeWidget#sendToBack(FakeWidget)
     */
    public void sendToBack(FakeWidget fakeWidget)
    {
        getRootWidget().sendToBack(fakeWidget);
    }
    
    /**
     * Override this method to do custom painting of your control.
     * It will automatically be called when necessary. (you don't need to 
     * register a PaintListener.)
     * @param e The PaintEvent triggering the drawing.
     */
    public void paintControl(PaintEvent e) {};
    
    /**
     * Mouse Double Click event.  Override to handle this event when no fake
     * widgets do.
     * @param e The event to handle.
     */
    public void mouseDoubleClick(MouseEvent e) {}
    
    /** Mouse down event.  Override to handle this event when no fake widgets
     * do.
     * @param e The event to handle.
     */
    public void mouseDown(MouseEvent e) {}
    
    /**
     * Mouse Up event.  Override to handle this event when no fake widgets do.
     * @param e The event to handle.
     */
    public void mouseUp(MouseEvent e) {}
    
    /** Root widget class is like a normal widget, but it calls methods on the
     * actual canvas rather than it's parent widget. */
    private static final class RootWidget extends FakeWidget
    {
        private FakeCanvas canvas;
        /** Mouse Listener type */
        private static abstract class MouseEventListener implements MouseTrackListener, MouseMoveListener {}

        /** Mouse Listener that listens to events and triggers notifications */
        private MouseEventListener mouseListener = new MouseEventListener()
        {
            public void mouseEnter(MouseEvent e)
            {
                if (!mouseInside)
                    triggerMouseEntered(e);
            }
            
            public void mouseExit(MouseEvent e)
            {
                if (mouseInside)
                    triggerMouseExited(e);
            }
            
            /** Ignored for now. */
            public void mouseHover(MouseEvent e){}
            
            public void mouseMove(MouseEvent e)
            {
                triggerMouseMoved(e);
            }
        };
        
        /** Listens to the parent canvas and stretches the root pane to be the same
         * size whenever the parent resizes. */
        private Listener resizeListener = new Listener()
        {
            public void handleEvent(Event event)
            {
                Point size = canvas.getSize();
                bounds.width = size.x;
                bounds.height = size.y;
            }
        };
        
        public RootWidget(FakeCanvas canvas)
        {
            this.canvas = canvas;
            canvas.addListener(SWT.Resize, resizeListener);
            checkListening();
        }
        
        /** Causes the specified rectangle to be redrawn
         * @param x The x-coordinate, relative to this control, that the rectangle
         * needing to be redrawn begins at.
         * @param y The y-coordinate.
         * @param width The width of the rectangle to redraw
         * @param height The height of the rectangle to redraw.
         */
        public void redraw(int x, int y, int width, int height)
        {
            canvas.redraw(bounds.x+x, bounds.y+y, width, height, true);
        }
        
        /** Check to see if we should be listening, and start listening if we
         * are, or stop if we shouldn't be. */
        protected void checkListening()
        {
            // FakeWidget, our super class, calls this in <init>.  Whoops. We'll
            // call it at the end of our constructor, after canvas != null.
            if (canvas == null)
                return;
            if (listening)
            {
                if (mouseListeners.isEmpty() && mouseMotionListeners.isEmpty())
                {
                    listening = false;
                    canvas.removeMouseMoveListener(mouseListener);
                    canvas.removeMouseTrackListener(mouseListener);
                }
            } else {
                if (!mouseListeners.isEmpty() || !mouseMotionListeners.isEmpty())
                {
                    listening = true;
                    canvas.addMouseMoveListener(mouseListener);
                    canvas.addMouseTrackListener(mouseListener);
                }
            }
        }
        
        /** Return the SWT display of the canvas we're in. */
        public Display getDisplay()
        {
            return canvas.getDisplay();
        }
        
        /** Return the canvas we're in. */
        public FakeCanvas getCanvas()
        {
            return canvas;
        }
    
        /**
         * Converts a point to the coordinate system of the canvas we're inside.
         */
        public Point convertPoint(Point point)
        {
            return new Point(point.x, point.y);
        }
    
        /** Lets clicks fall through to canvas by returning false. */
        public boolean mouseDoubleClick(MouseEvent e) { return false; }
        /** Lets clicks fall through to canvas by returning false. */
        public boolean mouseDown(MouseEvent e) { return false; }
        /** Lets clicks fall through to canvas by returning false. */
        public boolean mouseUp(MouseEvent e) { return false; }
    }
}
